/**
 *
 * The MIT License
 *
 * Copyright (c) 2008 Board of Trustees, Leland Stanford Jr. University. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.macdadi.ks.client.views;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import jmaki.charting.client.Axes;
import jmaki.charting.client.Axis;
import jmaki.charting.client.DataSet;
import jmaki.charting.client.PieChart;
import org.macdadi.ks.client.Main;
import org.macdadi.ks.client.View;
import org.macdadi.ks.client.components.DefaultItemEditor;
import org.macdadi.ks.client.components.EditableTree;
import org.macdadi.ks.client.data.*;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * @author Ben Suter <ben.suter@gmail.com>
 */
public class Preferences extends View implements TreeListener
{
    protected VerticalPanel chartPanel;

    protected EditableTree tree;
    protected ArrayList expandedItems = new ArrayList();
    protected ProjectInfo project;
    protected AuthenticatedUser currentUser;

    protected EditableTree.ItemEditor preferenceEdit;
        
    protected HashMap prefsByGoal = new HashMap();
    protected final int maxBudget = 100;
    protected int remainingBudget;
    
    public static ViewInfo init()
    {
        String info = "<p>Define your preferences for each goal</p>";

        return new ViewInfo("Preferences", info)
        {
            public View createInstance()
            {
                return new Preferences();
            }

            public String getColor()
            {
                return "#2fba10";
            }
        };
    }

    public Preferences()
    {
        project = Main.getCurrentProject();

        HorizontalPanel mainPanel = new HorizontalPanel();
        mainPanel.setStyleName("macdadi-Preferences");
        mainPanel.setWidth("100%");

        tree = new EditableTree();
        mainPanel.add(tree);
        tree.addTreeListener(this);

        preferenceEdit = new PreferenceEdit(updateView, project);

        chartPanel = new VerticalPanel();
        mainPanel.add(chartPanel);

        initWidget(mainPanel);
    }

    public void onShow() {
        currentUser = Main.getCurrentUser();

        /* This view displays nothing if user is not authenticated */
        if ( currentUser == null )
            return;
        
        populateTree(project);
        populateChart();

        if ( currentUser.isAuthorizedOperation(project.getId(), AuthService.TARGET_PREFERENCES, AuthService.OPERATION_UPDATE))
            tree.addItemEditor(preferenceEdit);
    }

    public void onHide() {
        tree.stopEditing();
        tree.removeItemEditor(preferenceEdit);
        tree.clear();
        chartPanel.clear();
    }

    /**
     * A command that refreshes the view (tree and chart).
     */
    protected Command updateView = new Command() {
        public void execute() {
            tree.stopEditing();
            tree.clear();
            chartPanel.clear();
            populateTree(project);
            populateChart();
        }
    };

    /**
     * Add a new root item to the tree, whose label initially displays a "loading ..." message.
     * Then it retrieves all the root-level goals for this project and adds each one to the
     * tree. As it does so, the children of these goals are added recursively. See addChild().
     *
     * Finally the rootItem is expanded, so that the first level of goals
     * is always visible.
     *
     * @param project the project from which the tree data should be pulled
     */
    protected void populateTree(final ProjectInfo project) {

        final TreeItem rootItem = new TreeItem();
        rootItem.setText("Loading goals with preferences for " + currentUser.getName());
        tree.addItem(rootItem);

        DataService.App.getInstance().getPreferencesForUser(project.getId(), currentUser.getId(), Main.getCurrentAuthToken(), new AsyncCallback() {
            public void onFailure(Throwable caught)
            {
                GWT.log("Failed to populate the preferences table", caught);
                if (caught instanceof AuthorizationException) {
                    rootItem.setText("Please login to view preferences for " + project.getName());
                }
            }

            public void onSuccess(Object result)
            {
                Preference[] prefs = (Preference[]) result;

                remainingBudget = maxBudget;
                prefsByGoal.clear();

                Preference pref;
                for (int i = 0; i < prefs.length; i++) {
                    pref = prefs[i];
                    prefsByGoal.put(Integer.toString(pref.getGoal().getId()), pref);
                    remainingBudget -= pref.getValue();
                }
                loadGoalsWithPreferences(rootItem);
            }
        });
    }


    protected void loadGoalsWithPreferences(final TreeItem rootItem) {
        DataService.App.getInstance().getGoals(project.getId(), Main.getCurrentAuthToken(), new AsyncCallback() {
            public void onFailure(Throwable caught) {
                GWT.log("Failed to populate the goals/preferences tree", caught);
                if (caught instanceof AuthorizationException) {
                    rootItem.setText("Please login to view preferences for " + project.getName());
                }
            }

            public void onSuccess(Object result) {
                rootItem.setText("Preferences for " + currentUser.getName() + " - Remaining Budget: " + remainingBudget);

                Goal[] roots = (Goal[]) result;
                for (int i = 0; i < roots.length; i++) {
                    addChild(rootItem, roots[i], true);
                }

                /* Expand the first and second levels */
                rootItem.setState(true, true);
                for (int i = 0; i < rootItem.getChildCount(); i++ ) {
                    rootItem.getChild(i).setState(true, true);
                }
            }
        });
    }

    /**
     * Adds the given Goal as a child TreeItem to the parent TreeItem.
     * Optionally steps into the given Goal's children and does the same there,
     * etc. recursively.
     *
     * @param parent  the tree node to act as parent for the newly added goal
     * @param child   the goal to be added as a child node to the tree
     * @param recurse if true, will call this method for the child and each of its children, etc.
     *
     * @return the aggregated preference value of this node
     */
    protected int addChild(TreeItem parent, Goal child, boolean recurse) {
        TreeItem node = new TreeItem();
        node.setUserObject(child);
        parent.addItem(node);

        int aggregateValue = 0;
        if (recurse) {
            for (int i = 0; i < child.getNumChildren(); i++)
                aggregateValue += addChild(node, child.getChild(i), true);
        }


        Preference pref;
        pref = (Preference) prefsByGoal.get(Integer.toString(child.getId()));
        if ( pref != null) {
            /* If this goal has a specific preference assigned, its value is taken
             * as the associated preference. Otherwise its value is taken as the
             * aggregated values of its sub-goals.
             */
            aggregateValue = pref.getValue();
        }

        String label;
        label = child.getName();
        label += ": " + aggregateValue;
        node.setText(label);

        /* If this node, as identified by its goal's id, was "expanded" in a
         * previous incarnation of this tree, then expand it here again.
         */
        if (expandedItems.contains(Integer.toString(child.getId())))
            node.setState(true, false);

        return aggregateValue;
    }


    public void onTreeItemSelected(TreeItem item) {
        // do nothing
    }

    /**
     * Remember the expansion state of this item, so that we can later
     * return the tree to the same state when it is refreshed.
     *
     * @param item the item whose expanded/folded state changed
     */
    public void onTreeItemStateChanged(TreeItem item) {
        /* Remember the expansion state for later tree refresh, based on goal ID. */
        Goal g;
        if (item.getUserObject() instanceof Goal)
            g = (Goal) item.getUserObject();
        else
            return;

        if (item.getState())
            expandedItems.add(Integer.toString(g.getId()));
        else
            expandedItems.remove(Integer.toString(g.getId()));
    }



    /**
     * Display aggregate preference values, as a pie chart. All leaf
     * nodes are aggregated and each of their parents is displayed as
     * a piece of the pie.
     */
    protected void populateChart()
    {
        chartPanel.clear();

//        /* An example pie chart, adapted from example */
//        String[] labels = {"May", "June", "July", "August"};
//        Axes axes = new Axes(new Axis(labels));
//        final PieChart chart = new PieChart(400, 400, axes, PieChart.RED);
//        double[] points = {250, 35, 435, 15};
//        DataSet ds = new DataSet("prices", "Some prices", points);
//        chart.setDataSet(ds);
//        chartPanel.add(chart);
        
//          /* An example bar chart, for testing */
//        String[] xLabels = new String[] { "x1", "x2", "x3", "x4", "x5", "x6"};
//        String[] yLabels = new String[] { "y1", "y2", "y3", "y4", "y5", "y6"};
//        Axes axes = new Axes(new Axis(xLabels), new Axis(yLabels));
//        BarChart chart = new BarChart(400, 400, axes, BarChart.BLUE);
//        double[] data1 = new double[] { 1.0, 2.0, 3.0, 2.0, 1.0, 0.5 };
//        DataSet ds1 = new DataSet("ds1", "DataSet 1", data1);
//        chart.addDataSet(ds1);
//        chartPanel.add(chart);

        DataService.App.getInstance().getAggregatePreferencesForUser(project.getId(), currentUser.getId(), Main.getCurrentAuthToken(), new AsyncCallback()
        {
            public void onFailure(Throwable caught)
            {
                GWT.log("Failed to populate the preferences chart", caught);
                if (caught instanceof AuthorizationException) {
                    // TODO: do not use Window.alert for error feedback
                    Window.alert("Please login to view preferences for " + project.getName());
                }
            }

            public void onSuccess(Object result)
            {
                Preference[] prefs = (Preference[]) result;
                drawChart(prefs);
            }
        });

    }

    protected void drawChart(Preference[] prefs)
    {
        int numPrefs = prefs.length;
        String[] goalNames = new String[numPrefs];
        double[] goalPrefs = new double[numPrefs];

        Preference p;
        for (int i = 0; i < numPrefs; i++ ) {
            p = prefs[i];
            goalNames[i] = p.getGoal().getName();
            goalPrefs[i] = p.getValue();
        }

        Axes axes = new Axes(new Axis(goalNames), new Axis(new String[] {}));
        /* Note: If we don't set the size explicitly, the chart collapses */
        PieChart chart = new PieChart(600, 400, axes, PieChart.GREEN);
        DataSet data = new DataSet("aggregatePrefs", "Aggregate Preferences", goalPrefs);
        chart.setDataSet(data);

        chartPanel.add(chart);
    }


    /**
     * Lets you edit a user preference value for a goal.
     * Only matches to leaf goals.
     */
    public class PreferenceEdit extends DefaultItemEditor {
        protected ProjectInfo project;

        public PreferenceEdit(Command onSuccess, ProjectInfo p) {
            super(DefaultItemEditor.iconPathEdit, "Set preference", onSuccess);
            this.project = p;
        }

        protected Widget getInputWidget(Object userObject) {
            if (userObject == null || !(userObject instanceof Goal))
                throw new IllegalArgumentException("userObject must be a valid Goal");

            return configureWidget((Goal) userObject);
        }

        public boolean matchesItem(TreeItem item) {
            /* Only display this for leaf items */
            return item.getChildCount() == 0;
        }

        protected Widget configureWidget(final Goal goal) {
            /* get preference for this goal */
            final Preference pref;
            pref = (Preference) prefsByGoal.get(Integer.toString(goal.getId()));

            /* A panel for adding a new member */
            VerticalPanel vp = new VerticalPanel();

            final ListBox lb = new ListBox();
            fillAvailablePreferenceValues(lb, pref);   /* Also sets the selected index */
            vp.add(lb);

            Button btnAdd = new Button("Apply");
            btnAdd.addClickListener(new ClickListener() {
                public void onClick(Widget sender) {
                    int newValue = -1;
                    newValue = Integer.parseInt(lb.getValue(lb.getSelectedIndex()));
                    updatePreference(pref, newValue);
                }
            });
            vp.add(btnAdd);

            return vp;
        }

        protected void updatePreference(final Preference pref, int newValue) {
            if ( newValue == pref.getValue() )
                return;

            DataWriteService.App.getInstance().updatePreference(project.getId(), pref.getGoal().getId(), pref.getUser().getId(), newValue, Main.getCurrentAuthToken(), this);
        }

        protected void fillAvailablePreferenceValues(final ListBox choices, Preference pref) {
            choices.addItem("0");
            choices.setSelectedIndex(0); /* This may get changed below */

            int currentVal = pref.getValue();
            int maxVal = currentVal + remainingBudget;
            for (int i = 1; i <= maxVal; i++ ) {
                choices.addItem(Integer.toString(i));
                if ( i == currentVal )
                    choices.setSelectedIndex(i);
            }
        }
    }
}